// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: junglekim
#include "cc/appcoin2/support/mm_helper/mm_educator_strategy.h"

namespace coin2::appcoin2::support::mm_helper {

void MMEducatorStrategy::Init(Driver* driver) {
  driver_ = driver;
  auto get_product_holder =
      [this](const coin2::exchange::base::symbology::IProduct& product, int64_t timestamp) {
        return this->driver_->product_holder_manager()->GetProductHolder(product, timestamp);
      };

  auto driverConfig = driver->config();
  CHECK_EQ(driverConfig.order().exchanges().size(), 1);
  CHECK_EQ(driverConfig.order().exchanges().cbegin()->second.products().norms().size(), 1);
  auto symbol_str = driverConfig.order().exchanges().cbegin()->second.products().norms()[0];
  exe_config_.set_mea(driverConfig.order().exchanges().cbegin()->second.mea());
  exe_config_.set_symbol(symbol_str);
  exe_config_.set_lot_size(lot_size_);
  exe_config_.set_order_update_period(order_update_period_);

  if (!start_datetime_.empty()) { timestamp0_ = Readable2Epoch(start_datetime_); }

  auto mea = MarketExchangeApi::FromString(driverConfig.order().exchanges().cbegin()->second.mea());
  executor_ = std::make_unique<LayeringExecutor2>(
      exe_config_,
      driver->order()->GetUniqueSystem(mea),
      std::nullopt,
      nullptr,
      get_product_holder);

  int64_t ts = GetCurrentTimestamp();
  auto product = CreateProductFromNormString(mea, symbol_str, ts);
  symbol_ = GetSymbol(*product);
  ProductEncyclopedia product_cache;
  pi_ = &(product_cache.holder(*CreateProductFromUniqueString(symbol_, ts)).product_info());
  CHECK(lot_size_ > pi_->tick_qty().submittable_min_qty(buy_price_ + sell_price_)) << "lot_size must be greater than minimum qty";
  product_holder_ = driver->product_holder_manager()->GetProductHolder(*product, ts);
  auto& request = os()->GetAccountRequest();
  owner_ = request.owner();
}

void MMEducatorStrategy::onCleanup(Driver*) {
  LOG(INFO) << "CLEANING UP.";
  executor_->ForceCancelAllWorkingOrders();
  Report();
}

void MMEducatorStrategy::onBookFeed(const FeedUpdate& upd) {
  int64_t timestamp = upd.timestamp();

  if (!os()->is_ready()) return;
  if (!(timestamp0_ < timestamp)) {
    LOG(INFO) << fmt::format(
      "start_datetime not satisfied yet (or maybe wrong datetime).\nstart_datetime: {}\ntimestamp0: {}",
      start_datetime_,
      timestamp0_);
    return;
  }
  if (done_) {
    if (timestamp - last_report_ts_ > 30e9) {
      LOG(INFO) << "DONE!!!!!";
      Report();
      last_report_ts_ = timestamp;
    }
    return;
  }
  if (!upd.book().Ask0().has_value() || !upd.book().Bid0().has_value()) {
    LOG(INFO) << fmt::format(
        "bid{} ask{} has no value, skipping",
        upd.book().Bid0().has_value(),
        upd.book().Ask0().has_value());
    return;
  }

  midp_ = (upd.book().Ask0()->price + upd.book().Bid0()->price) * 0.5;
  if (buy_price_ && ((buy_price_ - midp_) * lot_size_ > max_loss_quote_)) {
    LOG(INFO) << fmt::format(
        "buy price too high. Please TERMINATE. \nbuy_price: {}\nmid price: {}\nmax_loss_quote: {}\nmax_loss: {}",
        buy_price_,
        midp_,
        max_loss_quote_,
        (buy_price_ - midp_) * lot_size_);
    return;
  }
  if (sell_price_ && ((midp_ - sell_price_) * lot_size_ > max_loss_quote_)) {
    LOG(INFO) << fmt::format(
        "sell price too low. Please TERMINATE. \nsell_price: {}\nmid price: {}\nmax_loss_quote: {}\nmax_loss: {}",
        sell_price_,
        midp_,
        max_loss_quote_,
        (midp_ - sell_price_) * lot_size_);
    return;
  }
    
  if (buy_price_) {
    executor_->ForceFireBuyOrder(timestamp, buy_price_, lot_size_, symbol_);
    LOG(INFO) << "BUY AGGRESSION ORDER SENT";  
  } else if (sell_price_) {
    executor_->ForceFireSellOrder(timestamp, sell_price_, lot_size_, symbol_);
    LOG(INFO) << "SELL AGGRESSION ORDER SENT";  
  }
  done_ = true;  
}

void MMEducatorStrategy::onAccountOrder(const OrderUpdate& upd) {
  if (upd.product_order_info().event().type() == coin::proto::OrderEvent::ORDER_FILLED) {
    auto oe = upd.product_order_info().event();
    double qty = oe.fill_qty();
    double price = oe.fill_price();

    fills_[price] += qty;
    executor_->onAccountOrder(upd);
    Report();
  }
}

void MMEducatorStrategy::onAccountInfo(const OrderUpdate& upd) {
  if (!os()->is_ready()) {
    return;
  }
  executor_->onAccountInfo(upd);
  if (!done_) {
    LOG(INFO) << "WAITING FOR MID PRICE(BOOKFEED) !!!";
  }
}

void MMEducatorStrategy::Report() {
  nlohmann::json j;
  auto& info = j["INFO"];
  info["SYMBOL"] = symbol_;
  info["OWNER"] = owner_;
  info["BUY_PRICE"] = buy_price_;
  info["SELL_PRICE"] = sell_price_;
  info["QTY"] = lot_size_;
  
  auto& fills = j["FILLED"];
  for (auto& [price, qty] : fills_) {
    fills[std::to_string(RoundUp(price * qty, 2))] = RoundUp(price, 6);
  }
  LOG(INFO) << j.dump(2);
}

IOrderSubsystem* MMEducatorStrategy::os() {
  auto mea =
      MarketExchangeApi::FromString(driver_->config().order().exchanges().cbegin()->second.mea());
  return driver_->order()->GetUniqueSystem(mea);
}

int64_t MMEducatorStrategy::Readable2Epoch (std::string readable) {
  struct tm time;
  int64_t result;
  strptime(readable.c_str(), "%Y-%m-%d %H:%M:%S", &time);
  result = (mktime(&time) - timezone) * 1'000'000'000LL;
  return result;
}

std::string MMEducatorStrategy::GetSymbol(const IProduct& product) {
  return product.market_name() + ":" + product.exchange_name() + ":" + product.absolute_norm();
}

double MMEducatorStrategy::RoundUp(double value, int decimal) {
  const double multiplier = std::pow(10.0, decimal);
  return std::ceil(value * multiplier) / multiplier;
}
}  // namespace coin2::appcoin2::support::mm_helper
